package com.tianruan.dc.common.config;

import com.alibaba.fastjson.JSONObject;
import com.tianruan.dc.common.property.OAuthProperty;
import com.tianruan.dc.common.utils.DateUtils;
import com.tianruan.dc.modules.sys.user.security.Auth2ResponseExceptionTranslator;
import com.tianruan.dc.modules.sys.user.security.CustomerAuthenticationEntryPoint;
import com.tianruan.dc.modules.sys.user.security.CustomerRestAccessDeniedHandler;
import com.tianruan.dc.modules.sys.user.security.LogoutSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.AuthenticationEntryPoint;

import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * @PS 安全认证类
 */
@Configuration
@Order(-1)
public class SecurityOAuthConfig {

    private static final String DEMO_RESOURCE_ID = "order";



    /**
     * 资源服务器配置
     */
    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

        @Autowired
        private OAuthProperty oAuthProperty;

        @Autowired
        private CustomerAuthenticationEntryPoint customerAuthenticationEntryPoint;

        @Autowired
        private CustomerRestAccessDeniedHandler customerRestAccessDeniedHandler;


        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            // 定义异常转换类生效
            AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
            ((OAuth2AuthenticationEntryPoint) authenticationEntryPoint).setExceptionTranslator(new Auth2ResponseExceptionTranslator());
            resources.authenticationEntryPoint(authenticationEntryPoint)
                    .resourceId(DEMO_RESOURCE_ID)
                    .stateless(true);
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
                    .authorizeRequests()
                    .antMatchers("/static/**").permitAll()
                    .antMatchers("/api/**")
                    .authenticated();

            http
                    .exceptionHandling()
                    .accessDeniedHandler(customerRestAccessDeniedHandler)
                    .authenticationEntryPoint(customerAuthenticationEntryPoint);
            http
                    .logout()
                    .logoutSuccessHandler(new LogoutSuccessHandler());

            http
                    .httpBasic()
                    //未登录时，进行json格式的提示，很喜欢这种写法，不用单独写一个又一个的类
                    .authenticationEntryPoint((request,response,authException) -> {
                        response.setContentType("application/json;charset=utf-8");
                        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                        PrintWriter out = response.getWriter();
                        Map<String,Object> map = new HashMap<String,Object>();
                        map.put("code", 403);
                        map.put("message","未登录");
                        out.write(JSONObject.toJSONString(map));
                        out.flush();
                        out.close();
                    });


        }
    }

    /**
     * 认证服务器
     */
    @Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

        @Autowired
        AuthenticationManager authenticationManager;
        @Autowired
        RedisConnectionFactory redisConnectionFactory;
        @Autowired
        private OAuthProperty oAuthProperty;
        @Autowired
        private PasswordEncoder passwordEncoder;
        @Autowired
        private TokenStore tokenStore;
        @Autowired
        private TokenGranter tokenGranter;

        @Value("${oauth.config.accessToken-validitySeconds}")
        private Integer accessTokenValiditySeconds;
        @Value("${oauth.config.refreshToken-validitySeconds}")
        private Integer refreshTokenValiditySeconds;

        /**
         * 客户端配置（给谁发令牌）
         * @param clients
         * @throws Exception
         */
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            // 加密oauth密钥
            String finalSecret = passwordEncoder.encode(oAuthProperty.getSecret());

            clients.inMemory().withClient(oAuthProperty.getClient())
                    .resourceIds(DEMO_RESOURCE_ID)
                    .authorizedGrantTypes("password", "refresh_token")
                    .authorizedGrantTypes("smsCode", "wx-mini")
                    .scopes(oAuthProperty.getScope())
                    .authorities(oAuthProperty.getAuthorities())
                    .secret(finalSecret)
                    .accessTokenValiditySeconds(accessTokenValiditySeconds == null ? (int)(DateUtils.DAY_TIMESTAMP / 1000) : accessTokenValiditySeconds)
                    .refreshTokenValiditySeconds(refreshTokenValiditySeconds == null ? (int)(DateUtils.DAY_TIMESTAMP / 1000) * 30: refreshTokenValiditySeconds);
        }

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {

            endpoints
                    .tokenGranter(tokenGranter)
                    .tokenStore(tokenStore)
                    .authenticationManager(authenticationManager)
                    .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
        }

        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
            //允许表单认证
            oauthServer.allowFormAuthenticationForClients();
        }

    }

}
